﻿// --------------------------------------------------------------------------------
// SharpDisasm (File: SharpDisasm\translator.cs)
// Copyright (c) 2014-2015 Justin Stenning
// http://spazzarama.com
// https://github.com/spazzarama/SharpDisasm
// https://sharpdisasm.codeplex.com/
//
// SharpDisasm is distributed under the 2-clause "Simplified BSD License".
//
// Portions of SharpDisasm are ported to C# from udis86 a C disassembler project
// also distributed under the terms of the 2-clause "Simplified BSD License" and
// Copyright (c) 2002-2012, Vivek Thampi <vivek.mt@gmail.com>
// All rights reserved.
// UDIS86: https://github.com/vmt/udis86
//
// Redistribution and use in source and binary forms, with or without modification, 
// are permitted provided that the following conditions are met:
// 
// 1. Redistributions of source code must retain the above copyright notice, 
//    this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice, 
//    this list of conditions and the following disclaimer in the documentation 
//    and/or other materials provided with the distribution.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// --------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace SharpDisasm.Translators
{
    /// <summary>
    /// All translators must inherit from the abstract class <see cref="Translator"/>. This base class provides a number of common methods, and defines two abstract methods that must be implemented.
    /// </summary>
    /// <threadsafety static="true" instance="false" />
    public abstract class Translator
    {
        /// <summary>
        /// The content being generated by the current call to the translator
        /// </summary>
        protected StringBuilder Content = new StringBuilder();

        /// <summary>
        /// A list of all registers names
        /// </summary>
        protected static readonly string[] Registers = Udis86.syn.ud_reg_tab;

        /// <summary>
        /// Indicates whether the generated output will include the instruction address
        /// </summary>
        public bool IncludeAddress { get; set; }

        /// <summary>
        /// Indicates whether the generated output will include the original binary instruction
        /// </summary>
        public bool IncludeBinary { get; set; }

        /// <summary>
        /// Indicates whether the generated output will resolve instruction that contains RIP register to absolute address
        /// </summary>
        public bool ResolveRip { get; set; }

        /// <summary>
        /// An optional symbol resolver <see cref="SharpDisasm.Translators.SymbolResolverDelegate"/>.
        /// </summary>
        public SymbolResolverDelegate SymbolResolver { get; set; }

        /// <summary>
        /// Translate a list of instructions separated by <see cref="Environment.NewLine"/>.
        /// </summary>
        /// <param name="insns">The instructions to translate</param>
        /// <returns>Each of the translated instructions separated by <see cref="Environment.NewLine"/></returns>
        public virtual string Translate(IEnumerable<Instruction> insns)
        {
            Content.Length = 0;
            bool first = true;
            foreach (var insn in insns)
            {
                if (first)
                    first = false;
                else
                    Content.Append(Environment.NewLine);

                if (IncludeAddress)
                    WriteAddress(insn);
                if (IncludeBinary)
                    WriteBinary(insn);

                TranslateInstruction(insn);
                TrimContent();
            }
            var result = Content.ToString();
            Content.Length = 0;
            return result;
        }

        /// <summary>
        /// Translate a single instruction
        /// </summary>
        /// <param name="insn">The instruction to translate</param>
        /// <returns>The result of the single translated instruction</returns>
        public virtual string Translate(Instruction insn)
        {
            Content.Length = 0;
            if (IncludeAddress)
                WriteAddress(insn);
            if (IncludeBinary)
                WriteBinary(insn);
            TranslateInstruction(insn);
            TrimContent();
            var result = Content.ToString();
            Content.Length = 0;
            return result;
        }

        /// <summary>
        /// Gets a printable version of an instruction's address.
        /// </summary>
        public virtual string TranslateAddress(Instruction insn)
        {
            Content.Length = 0;
            WriteAddress(insn);
            TrimContent();
            var result = Content.ToString();
            Content.Length = 0;
            return result;
        }

        /// <summary>
        /// Gets a printable version of an instruction's bytes.
        /// </summary>
        public virtual string TranslateBytes(Instruction insn)
        {
            Content.Length = 0;
            WriteBinary(insn);
            TrimContent();
            var result = Content.ToString();
            Content.Length = 0;
            return result;
        }

        /// <summary>
        /// Gets a printable version of an instruction's mnemonic opcode.
        /// </summary>
        public virtual string TranslateMnemonic(Instruction insn)
        {
            Content.Length = 0;
            TranslateInstruction(insn);
            TrimContent();
            var result = Content.ToString();
            Content.Length = 0;
            return result;
        }

        /// <summary>
        /// Each translator will translate an instruction here.
        /// </summary>
        protected abstract void TranslateInstruction(Instruction insn);

        /// <summary>
        /// Converts a <see cref="SharpDisasm.Udis86.ud_type"/> into an index into <see cref="Registers"/> and returns the result.
        /// </summary>
        /// <param name="type">The register <see cref="SharpDisasm.Udis86.ud_type"/> to retrieve the corresponding string for. Note: only the UD_R_* types will result in a valid index.</param>
        /// <returns>The corresponding string value for the register.</returns>
        protected string RegisterForType(SharpDisasm.Udis86.ud_type type)
        {
            // Adjust to be zero based (i.e. the first register in ud_type starts at 1 == UD_R_AL)
            int indx = (type - SharpDisasm.Udis86.ud_type.UD_R_AL);
            return Registers[indx];
        }

        /// <summary>
        /// Writes the address of the instruction to <see cref="Content"/>. The width of the address is determined by the <see cref="ArchitectureMode"/> used during disassembly.
        /// </summary>
        /// <param name="insn">The instruction to append the address of.</param>
        protected void WriteAddress(Instruction insn)
        {
            switch (insn.dis_mode)
            {
                case ArchitectureMode.x86_16:
                    Content.AppendFormat("{0:x4} ", insn.Offset);
                    break;
                case ArchitectureMode.x86_32:
                    Content.AppendFormat("{0:x8} ", insn.Offset);
                    break;
                case ArchitectureMode.x86_64:
                    Content.AppendFormat("{0:x16} ", insn.Offset);
                    break;
            }
        }

        /// <summary>
        /// Writes the instruction binary data to <see cref="Content"/>. The result is padded to 20 characters (supporting instructions up to 10-bytes long). If the instruction was disassembled without <see cref="Disassembler.CopyBinaryToInstruction"/> a blank 20 character string will be appended.
        /// </summary>
        /// <param name="insn">The instruction of which to append the binary data for</param>
        protected void WriteBinary(Instruction insn)
        {
            if (insn.Bytes != null)
            {
                Content.AppendFormat("{0,-30} ", String.Join("", (from b in insn.Bytes
                                                                   select String.Format("{0:x2} ", b)).ToArray()));
            }
        }

        /// <summary>
        /// Removes spaces from the end of 'Content' stringbuilder.
        /// </summary>
        protected void TrimContent()
        {
            // the Translate() methods tend to add a trailing space,
            // so remove it
            while (Content.Length > 0 && Content[Content.Length - 1] == ' ')
            {
                Content.Length -= 1;
            }
        }

        /// <summary>
        /// TODO: document and rename ported translator methods
        /// </summary>
        /// <param name="insn"></param>
        /// <param name="opr"></param>
        /// <returns></returns>
        protected ulong ud_syn_rel_target(Instruction insn, Operand opr)
        {
            ulong trunc_mask = 0xffffffffffffffff >> (64 - insn.opr_mode);
            switch (opr.Size)
            {
                case 8: return (insn.PC + (ulong)opr.LvalSByte) & trunc_mask;
                case 16: return (insn.PC + (ulong)opr.LvalSWord) & trunc_mask;
                case 32: return (insn.PC + (ulong)opr.LvalSDWord) & trunc_mask;
                default:
                    throw new InvalidOperationException(string.Format("invalid relative offset size {0}.", opr.Size));
            }
        }

        /// <summary>
        /// TODO: document and rename ported translator methods
        /// </summary>
        /// <param name="insn"></param>
        /// <param name="addr"></param>
        protected void ud_syn_print_addr(Instruction insn, long addr)
        {
            string name = null;
            
            if (SymbolResolver != null)
            {
                long offset = 0;
                name = SymbolResolver(insn, addr, ref offset);
                if (!String.IsNullOrEmpty(name))
                {
                    if (offset > 0)
                    {
                        Content.AppendFormat("{0}{1:+#;-#}", name, offset);
                    }
                    else
                    {
                        Content.AppendFormat("{0}", name);
                    }
                    return;
                }
            }
            Content.AppendFormat("0x{0:x}", addr);
        }


        /// <summary>
        /// TODO: document and rename ported translator methods
        /// </summary>
        /// <param name="insn"></param>
        /// <param name="op"></param>
        protected void ud_syn_print_imm(Instruction insn, Operand op)
        {
            ulong v;
            if (op.Opcode == SharpDisasm.Udis86.ud_operand_code.OP_sI && op.Size != insn.opr_mode)
            {
                if (op.Size == 8)
                {
                    v = (ulong)op.LvalSByte;
                }
                else
                {
                    if (op.Size != 32)
                        throw new InvalidOperationException("Operand size must be 32");
                    v = (ulong)op.LvalSDWord;
                }
                if (insn.opr_mode < 64)
                {
                    v = v & ((1ul << insn.opr_mode) - 1ul);
                }
            }
            else
            {
                switch (op.Size)
                {
                    case 8: v = op.LvalByte; break;
                    case 16: v = op.LvalUWord; break;
                    case 32: v = op.LvalUDWord; break;
                    case 64: v = op.LvalUQWord; break;
                    default:
                        throw new InvalidOperationException(string.Format("Invalid size for operand: {0}", op.Size));
                }
            }
            Content.AppendFormat("0x{0:x}", v);
        }


        /// <summary>
        /// TODO: document and rename ported translator methods
        /// </summary>
        /// <param name="insn"></param>
        /// <param name="op"></param>
        /// <param name="sign"></param>
        protected void ud_syn_print_mem_disp(Instruction insn, Operand op, int sign)
        {
            if (op.Base == SharpDisasm.Udis86.ud_type.UD_NONE && op.Index == SharpDisasm.Udis86.ud_type.UD_NONE)
            {
                ulong v;
                /* unsigned mem-offset */
                switch (op.Offset)
                {
                    case 16: v = op.LvalUWord; break;
                    case 32: v = op.LvalUDWord; break;
                    case 64: v = op.LvalUQWord; break;
                    default:
                        throw new InvalidOperationException(string.Format("Invalid operand offset {0}", op.Offset));
                }
                Content.AppendFormat("0x{0:x}", v);
            }
            else
            {
                long v;
                switch (op.Offset)
                {
                    case 8: v = op.LvalSByte; break;
                    case 16: v = op.LvalSWord; break;
                    case 32: v = op.LvalSDWord; break;
                    default:
                        throw new InvalidOperationException(string.Format("Invalid operand offset {0}", op.Offset));
                }
                if (ResolveRip && op.Base == Udis86.ud_type.UD_R_RIP && op.Index == Udis86.ud_type.UD_NONE)
                {
                    v = v + (long)insn.PC;
                    Content.AppendFormat("0x{0:x}", v);
                }
                else
                {
                    if (v < 0)
                    {
                        Content.AppendFormat("-0x{0:x}", -v);
                    }
                    else if (v > 0)
                    {
                        Content.AppendFormat("{0}0x{1:x}", sign > 0 ? "+" : "", v);
                    }
                }
            }
        }
    }
}
